<?php
namespace dk\core;
class Jrelations{
    
    const HAS_ONE = 1;
    private $relations = array();
    private $confs = array();
    
    public function getToken(){
        return new JEvtToken();
    }
    
    /**
    * put your comment there...
    * 
    * @param mixed $relation
    */
    function addRelation($key, $relation, $conf = array()){
        if(!isset($conf['mask'])){
            $conf['mask'] = 1;
        }
        
        $this->relations[$key] = $relation;
        $this->confs[$key] = $conf;
    }
    
    public function getConf($name = null, $key = null){
        if($name){
            if($key){
                return isset($this->confs[$name]) ? $this->confs[$name][$key] : null;
            }
            return isset($this->confs[$name]) ? $this->confs[$name] : null;
        } else {
            return $this->confs;
        }
    }
    
    function addRelationByconf($confs){
        foreach ($confs as $key => $conf) {
            $relation = self::createRelation($conf);
            $this->addRelation($key, $relation, $conf);
        }
    }
    
    static function createRelation($conf){
        if(!is_array($conf) || !isset($conf['type'])){
            JError::raiseError(501, 'Invalid relation');
        }
        
        $instance = null;
        switch ($conf['type']) {
            case self::HAS_ONE :
                $instance =  new JRelationHasOne($conf);
                break;
            default :
                JError::raiseError(501, 'Invalid relation type');
        }
        
        return $instance;
    }
    
    /**
    * put your comment there...
    * 
    * @param mixed $evt
    * @param JModelEvtToken $token
    */
    function update($evt, $token){
        if($evt == 'delete.after'){
            $evt = 'delete';
        }
        
        $rs = 0;
        $mask = $token->get('mask', 0);
        foreach ($this->relations as $key => $item) {
            $instance = $item;
            $conf = $this->confs[$key];
         
            if($mask && !($mask & $conf['mask'])){
                continue;
            }
            
            $m = 'on'. ucfirst($evt);
            if(method_exists($instance, $m)){
                $rs = (call_user_func(array($instance, $m), $token) | $rs);
            }
        }
        
        return $rs;
    }
    
    function getRelations(){
        return $this->relations;
    }
    
    /**
    * put your comment there...
    * 
    * @param mixed $key
    * @return JRelationAbstruct
    */
    function getRelation($key = null){
        if(isset($this->relations[$key])){
            return $this->relations[$key];
        }
        return null;
    }
    
    /**
    * $fieldMap = array(
    *       'field1' => 'relaction 1',
    *       'field2' => 'relaction 1',
    *       'field3' => 'relaction 2'
    *   )
    * 
    * @var mixed
    */
    protected $fieldMap = array();
    
    /**
    * put your comment there...
    * 
    * $relFieldLoaded = array(
    *       'relaction 1' => true,
    *       'relaction 2' => false,
    *   )
    * @var mixed
    */
    protected $relFieldLoaded = array();
    
    /**
    * put your comment there...
    * 
    * @param mixed $field
    * @return JRelationAbstruct
    */
    function getRelationFromField($field){
        if(isset($this->fieldMap[$field])){
            // find
            return $this->fieldMap[$field];
        }
        
        foreach ($this->relations as $relName => $rel) {
            if(isset($this->relFieldLoaded[$relName])){
                continue;
            }
            
            $this->relFieldLoaded[$relName] = true;
             
            /**
            * put your comment there...
            * 
            * @var JRelationAbstruct
            */
            $rel = $rel;
            $fileds = $rel->getForeignObj()->fields();
            
            // set
            foreach ($fileds as $f) {
                $this->fieldMap[$f] = $rel;
            }
            
            if(isset($this->fieldMap[$field])){
                return $this->fieldMap[$field]; // find
            }
        }
        
        return null;
    }
}

abstract class JRelationAbstruct{
    /**
    * put your comment there...
    * 
    * @var \sp\db\Table
    */
    protected $foreignObj;
    
    protected function getPk(){
        return $this->foreignObj->getPrimkey();
    }
    
    function getForeignObj(){
        return $this->foreignObj;
    }
    
    abstract function onInsert($args);
    abstract function onUpdate($args);
    abstract function onDelete($args);
    abstract function loadInfo($args);
}

class JRelationHasOne extends JRelationAbstruct{
    function loadInfo($id){
        $info = $this->foreignObj->getInfo($id, true);
        if(!$info){
            return array();
        }
        return $info;
    }
    
    protected $columns;
    function getColumns(){
        if(!$this->columns){
            $this->columns = $this->foreignObj->getColumns();
        }
        return $this->columns;
    }
    
    /**
    * put your comment there...
    * 
    * @param array $config
    * @return JRelationHasOne
    */
    public function __construct($config){
        if(!is_array($config)){
            JError::raiseError(501, 'Invalid arg, need array');
        }
        
        if(isset($config['table'])){
            if(!isset($config['forkey'])){
                JError::raiseError(501, 'Invalid arg, forkey is null');
            }
            
            $mod = table($config['table']);
            $mod->setPrimkey($config['forkey']);
        }elseif(isset($config['mod'])){
            if(!($config['mod'] instanceof JModelComm)){
                JError::raiseError(501, 'Invalid arg, mod is not instanceof JModelComm');
            }
            $mod = $config['mod'];
        }else{
            JError::raiseError(501, 'Invalid config, need table or mod');
        }
        $this->foreignObj = $mod;
    }

    /**
    * put your comment there...
    * 
    * @param JModelEvtToken $args
    */
    function onInsert($args) {
        $data = $args->get('data');
        $pk = $this->getPk();
        $data[$pk] = $args->get('id');
        $rs = $this->foreignObj->insert($data);
        return $rs[1];
    }
    
    /**
    * put your comment there...
    * 
    * @param JModelEvtToken $args
    */
    function onUpdate($args) {
        $id = $args->get('id');
        if(!is_numeric($id)){
            return false;
        }
        $data = $args->get('data');
        if($this->foreignObj->count($id)){
            return $this->foreignObj->update($data, $id);
        }else{
            $data[$this->getPk()] = $id;
            $f = $this->foreignObj->insert($data);
            $f = $f[1];
        }
        return $f;
    }
    
    function onDelete($args) {
        $id = $args->get('id');
        if(!$id){
            return false;
        }
        $cond = null;
        if(is_array($id)){
            $pk = $this->getPk();
            $cond = [
                $pk => $id
            ];
        } else if(is_numeric($id)){
            $cond = $id;
        }
        if($cond){
            return $this->foreignObj->delete($cond);
        }
    }
    
    function onSelect($id) {
        if(!is_numeric($id)){
            return false;
        }
        return $this->foreignObj->select($id)->current();
    }
}

class JEvtToken {
    protected $args = array();
    
    function set($key, $value){
        $this->args[$key] = $value;
    }
    
    function get($key, $def = null){
        if(isset($this->args[$key]))
            return $this->args[$key];
        return $def;
    }
    
    function __set($key, $value){
        $this->set($key, $value);
    }
    
    function __get($key){
        return $this->get($key);
    }
}
